/**
 * Copyright (c) 2013 onionframework.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.onionframework.core.optional;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class ImportBeanDefinitionParser implements BeanDefinitionParser {

	private Logger logger = LoggerFactory.getLogger(ImportBeanDefinitionParser.class);
	
	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {

		NodeList includes = element.getElementsByTagNameNS("http://www.onionframework.org/schema/core","include");
		NodeList excludes = element.getElementsByTagNameNS("http://www.onionframework.org/schema/core","exclude");
		
		Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
		
		Resource[] resources;
		for(int i=0;i<includes.getLength();i++){
			resources = resolveResources((Element)includes.item(i),null,parserContext);
			if(resources != null)
				Collections.addAll(actualResources, resources);
		}
		
		for(int i=0;i<excludes.getLength();i++){
			Resource[] excludeResources = resolveResources((Element)excludes.item(i),null,parserContext);
			for(Resource resource : excludeResources){
				actualResources.remove(resource);
				logger.info("忽略Spring配置文件{}",resource.toString());
			}
		}
		
		int importCount = 0;
		for(Resource resource : actualResources){
			importCount += parserContext.getReaderContext().getReader().loadBeanDefinitions(resource);
		}
		
		parserContext.getReaderContext().fireImportProcessed(parserContext.getReaderContext().getResource().toString(), 
				actualResources.toArray(new Resource[actualResources.size()]), 
				parserContext.extractSource(element));
		
		return null;
	}
	
	public static Resource[] resolveResources(Element element, String location, ParserContext parserContext){
		if(StringUtils.isBlank(location))
			location = element.getAttribute("resource");
		
		Assert.hasText(location);
		
		// Discover whether the location is an absolute or relative URI 
		boolean absoluteLocation = false;
		
		try {
			absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
		}
		catch (URISyntaxException ex) {
			// cannot convert to an URI, considering the location relative
			// unless it is the well-known Spring prefix "classpath*:"
		}
		
		if(absoluteLocation){
			return resolveAbsoluteLocationResources(parserContext, location);
		}else{
			// No URL -> considering resource location as relative to the current file.
			try {
				Resource relativeResource = parserContext.getReaderContext().getResource().createRelative(location);
				if (relativeResource.exists()) {
					return new Resource[]{relativeResource};
				}
				else {
					String baseLocation = parserContext.getReaderContext().getResource().getURL().toString();
					return resolveAbsoluteLocationResources(parserContext, baseLocation);
				}
			}
			catch (IOException ex) {
				parserContext.getReaderContext().error("Failed to resolve current resource location", element, ex);
			}
			catch (BeanDefinitionStoreException ex) {
				parserContext.getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
						element, ex);
			}
		}
		
		return null;
	}

	private static Resource[] resolveAbsoluteLocationResources(
			ParserContext parserContext, String location) {
		ResourceLoader resourceLoader = parserContext.getReaderContext().getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				return ((ResourcePatternResolver) resourceLoader).getResources(location);
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// Can only load single resources by absolute URL.
			return new Resource[]{resourceLoader.getResource(location)};
		}
	}

}
